#define MAX_INPUT 150.0f

typedef struct {
	int weightStartIndex;
	int inputStartIndex;
	int weightCount;
	int outputIndex;
} Neuron;

float vectorDot(__global float *v1, __global float *v2, int count) {
	float sum = 0;
	for (int i = 0; i < count; i++) {
		sum += v1[i] * v2[i];
	}
	return sum;
}

float activationFunction(float x) {
	return 1 / (1 + exp(-x));
}

float activationDerivative(float func) {
	return func * (1 - func);
}

__kernel void addError(
	__global float* errors, uint errorIndex, 
	__global float *weights, uint weightOffset,
	__global float *prevLayerErrors, uint prevErrorOffset) 
{
	uint i = get_global_id(0);
	float error = errors[errorIndex];
	prevLayerErrors[prevErrorOffset + i] += error * weights[weightOffset + i];
}

__kernel void scale(float factor, __global float *v, int offset) {
	uint i = get_global_id(0);
	v[i + offset] *= factor;
}

__kernel void scaleErrorByDerivative(__global Neuron *neurons, uint neuronsOffset, 
			__global float *errors, __global float *data) 
{
	uint j = get_global_id(0) + neuronsOffset;
	float neuronOutput = data[neurons[j].outputIndex];
	errors[j] *= activationDerivative(neuronOutput);
}

__kernel void calculateErrorGradient(
		__global Neuron *neurons, int neuronIndex,
		__global float *errors,
		__global float *data,
		__global float *derivatives) 
{
	uint i = get_global_id(0);
	float error = errors[neuronIndex];
	uint derivativeOffset = neurons[neuronIndex].weightStartIndex;
	uint inputOffset = neurons[neuronIndex].inputStartIndex;

	derivatives[derivativeOffset + i] -= error * data[inputOffset + i];
}

__kernel void activateNeuron(
    __global Neuron *neurons, int neuronStart,
    __global float *data, 
	__global float *weights, 
    __global float *neuronSumms)
{
    uint i = neuronStart + get_global_id(0);
    Neuron n = neurons[i];
    __global float* neuronInputs = &data[n.inputStartIndex];
    __global float* neuronWeights = &weights[n.weightStartIndex];
    float sum = vectorDot(neuronInputs, neuronWeights, n.weightCount);
    sum = clamp(sum, -MAX_INPUT, MAX_INPUT);
    neuronSumms[i] = sum;
    data[n.outputIndex] = activationFunction(sum);
}